/* Libreswan config file parser (confread.c)
 * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security
 * Copyright (C) 2004 Xelerance Corporation
 * Copyright (C) 2006-2008 Michael Richardson <mcr@xelerance.com>
 * Copyright (C) 2007 Ken Bantoft <ken@xelerance.com>
 * Copyright (C) 2006-2012 Paul Wouters <paul@xelerance.com>
 * Copyright (C) 2010 Michael Smith <msmith@cbnco.com>
 * Copyright (C) 2010 Tuomo Soini <tis@foobar.fi>
 * Copyright (C) 2012-2019 Paul Wouters <pwouters@redhat.com>
 * Copyright (C) 2012 Avesh Agarwal <avagarwa@redhat.com>
 * Copyright (C) 2012 Antony Antony <antony@phenome.org>
 * Copyright (C) 2013 Florian Weimer <fweimer@redhat.com>
 * Copyright (C) 2013 David McCullough <ucdevel@gmail.com>
 * Copyright (C) 2013-2019 D. Hugh Redelmeier <hugh@mimosa.com>
 * Copyright (C) 2016 Andrew Cagney <cagney@gnu.org>
 * Copyright (C) 2017-2018 Vukasin Karadzic <vukasin.karadzic@gmail.com>
 * Copyright (C) 2017 Mayank Totale <mtotale@gmail.com>
 * Copyright (C) 2020 Yulia Kuzovkova <ukuzovkova@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <https://www.gnu.org/licenses/gpl2.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>		/* for AF_UNSPEC */

#include "lswalloc.h"
#include "ip_address.h"
#include "ip_info.h"
#include "hunk.h"		/* for char_is_space() */
#include "ip_cidr.h"

#include "ipsecconf/confread.h"
#include "ipsecconf/starterlog.h"
#include "ipsecconf/interfaces.h"

#include "ipsecconf/keywords.h"
#include "ipsecconf/parser.h"	/* includes parser.tab.h generated by bison; requires keywords.h */

#include "whack.h" /* for DEFAULT_CTL_SOCKET */
#include "lswlog.h"
#ifdef USE_DNSSEC
# include <unbound.h>
# include <arpa/inet.h> /* for inet_ntop */
# include "dnssec.h"
#endif /* USE_DNSSEC */

/**
 * Set up hardcoded defaults, from data in programs/pluto/constants.h
 *
 * @param cfg starter_config struct
 * @return void
 */
static void ipsecconf_default_values(struct starter_config *cfg)
{
	static const struct starter_config empty_starter_config;	/* zero or null everywhere */
	*cfg = empty_starter_config;

	TAILQ_INIT(&cfg->conns);

	/* ==== config setup ==== */

# define SOPT(kbf, v)  { cfg->setup.options[kbf] = (v) ; }

	SOPT(KBF_LOGTIME, TRUE);
	SOPT(KBF_LOGAPPEND, TRUE);
	SOPT(KBF_LOGIP, TRUE);
	SOPT(KBF_AUDIT_LOG, TRUE);
	SOPT(KBF_UNIQUEIDS, TRUE);
	SOPT(KBF_LISTEN_UDP, TRUE);
	SOPT(KBF_LISTEN_TCP, FALSE);
	SOPT(KBF_DO_DNSSEC, TRUE);
	SOPT(KBF_PERPEERLOG, FALSE);
	SOPT(KBF_IKEBUF, IKE_BUF_AUTO);
	SOPT(KBF_IKE_ERRQUEUE, TRUE);
	SOPT(KBF_NFLOG_ALL, 0); /* disabled per default */
	SOPT(KBF_XFRMLIFETIME, XFRM_LIFETIME_DEFAULT); /* not used by pluto itself */
	SOPT(KBF_NHELPERS, -1); /* see also plutomain.c */

	SOPT(KBF_KEEPALIVE, 0);                  /* config setup */
	SOPT(KBF_DDOS_IKE_THRESHOLD, DEFAULT_IKE_SA_DDOS_THRESHOLD);
	SOPT(KBF_MAX_HALFOPEN_IKE, DEFAULT_MAXIMUM_HALFOPEN_IKE_SA);
	SOPT(KBF_SHUNTLIFETIME, PLUTO_SHUNT_LIFE_DURATION_DEFAULT);
	/* Don't inflict BSI requirements on everyone */
	SOPT(KBF_SEEDBITS, 0);
	SOPT(KBF_DROP_OPPO_NULL, FALSE);

#ifdef HAVE_LABELED_IPSEC
	SOPT(KBF_SECCTX, SECCTX);
#endif
	SOPT(KBF_DDOS_MODE, DDOS_AUTO);

	SOPT(KBF_OCSP_CACHE_SIZE, OCSP_DEFAULT_CACHE_SIZE);
	SOPT(KBF_OCSP_CACHE_MIN, OCSP_DEFAULT_CACHE_MIN_AGE);
	SOPT(KBF_OCSP_CACHE_MAX, OCSP_DEFAULT_CACHE_MAX_AGE);
	SOPT(KBF_OCSP_METHOD, OCSP_METHOD_GET);
	SOPT(KBF_OCSP_TIMEOUT, OCSP_DEFAULT_TIMEOUT);

	SOPT(KBF_SECCOMP, SECCOMP_DISABLED); /* will be enabled in the future */

# undef SOPT

	cfg->setup.strings[KSF_PLUTO_DNSSEC_ROOTKEY_FILE] = clone_str(DEFAULT_DNSSEC_ROOTKEY_FILE, "default dnssec rootkey file");
	cfg->setup.strings[KSF_NSSDIR] = clone_str(IPSEC_NSSDIR, "default ipsec nssdir");
	cfg->setup.strings[KSF_SECRETSFILE] = clone_str(IPSEC_SECRETS_FILE, "default ipsec.secrets file");
	cfg->setup.strings[KSF_DUMPDIR] = clone_str(DEFAULT_RUNDIR, "default dumpdir");
	cfg->setup.strings[KSF_IPSECDIR] = clone_str(IPSEC_CONFDDIR, "default ipsec.d dir");

	/* ==== end of config setup ==== */

	cfg->ctlsocket = clone_str(DEFAULT_CTL_SOCKET, "default control socket");

	/* ==== conn %default ==== */

	struct starter_conn *d = &cfg->conn_default;

# define DOPT(kbf, v)  { d->options[kbf] = (v); }

	DOPT(KNCF_NAT_KEEPALIVE, TRUE);    /* per conn */
	DOPT(KNCF_TYPE, KS_TUNNEL);

	DOPT(KNCF_INITIAL_CONTACT, FALSE);
	DOPT(KNCF_CISCO_UNITY, FALSE);
	DOPT(KNCF_NO_ESP_TFC, FALSE);
	DOPT(KNCF_VID_STRONGSWAN, FALSE);
	DOPT(KNCF_SEND_VENDORID, FALSE);

	DOPT(KNCF_REMOTEPEERTYPE, NON_CISCO);

	DOPT(KNCF_IKEPAD, TRUE);

	DOPT(KNCF_IKEV1_NATT, NATT_BOTH);
	DOPT(KNCF_ENCAPS, yna_auto);

	DOPT(KNCF_TCP, IKE_TCP_NO);
	DOPT(KNCF_REMOTE_TCPPORT, NAT_IKE_UDP_PORT);

	/* Network Manager support */
#ifdef HAVE_NM
	DOPT(KNCF_NMCONFIGURED, FALSE);
#endif

	DOPT(KNCF_XAUTHBY, XAUTHBY_FILE);
	DOPT(KNCF_XAUTHFAIL, XAUTHFAIL_HARD);

	DOPT(KNCF_NIC_OFFLOAD, yna_auto);
	DOPT(KNCF_IKELIFETIME, IKE_SA_LIFETIME_DEFAULT);

	DOPT(KNCF_REPLAY_WINDOW, IPSEC_SA_DEFAULT_REPLAY_WINDOW);

	DOPT(KNCF_RETRANSMIT_TIMEOUT, RETRANSMIT_TIMEOUT_DEFAULT);
	DOPT(KNCF_RETRANSMIT_INTERVAL_MS, RETRANSMIT_INTERVAL_DEFAULT_MS);

	DOPT(KNCF_SALIFETIME, IPSEC_SA_LIFETIME_DEFAULT);
	DOPT(KNCF_REKEYMARGIN, SA_REPLACEMENT_MARGIN_DEFAULT);
	DOPT(KNCF_REKEYFUZZ, SA_REPLACEMENT_FUZZ_DEFAULT);

	DOPT(KNCF_KEYINGTRIES, SA_REPLACEMENT_RETRIES_DEFAULT);

	DOPT(KNCF_HOSTADDRFAMILY, AF_UNSPEC);
	DOPT(KNCF_CLIENTADDRFAMILY, AF_UNSPEC);

	DOPT(KNCF_AUTO, STARTUP_IGNORE);
	DOPT(KNCF_XFRM_IF_ID, UINT32_MAX);

# undef DOPT

	d->ike_version = IKEv2;
	d->policy =
		POLICY_TUNNEL |
		POLICY_ECDSA | POLICY_RSASIG | POLICY_RSASIG_v1_5 | /* authby= */
		POLICY_ENCRYPT | POLICY_PFS |
		POLICY_IKE_FRAG_ALLOW |      /* ike_frag=yes */
		POLICY_ESN_NO;      	     /* esn=no */

	d->sighash_policy =
		POL_SIGHASH_SHA2_256 | POL_SIGHASH_SHA2_384 | POL_SIGHASH_SHA2_512;

	d->left.host_family = &ipv4_info;
	d->left.addr = ipv4_info.address.any;
	d->left.nexttype = KH_NOTSET;
	d->left.nexthop = ipv4_info.address.any;

	d->right.host_family = &ipv4_info;
	d->right.addr = ipv4_info.address.any;
	d->right.nexttype = KH_NOTSET;
	d->right.nexthop = ipv4_info.address.any;

	d->xfrm_if_id = UINT32_MAX;

	/* default is NOT to look in DNS */
	d->left.key_from_DNS_on_demand = FALSE;
	d->right.key_from_DNS_on_demand = FALSE;

	d->state = STATE_LOADED;

	d->left.authby = AUTHBY_UNSET;
	d->right.authby = AUTHBY_UNSET;

	d->left.updown = clone_str(DEFAULT_UPDOWN, "conn default left updown");
	d->right.updown = clone_str(DEFAULT_UPDOWN, "conn default right updown");
	/* ==== end of conn %default ==== */
}

/*
 * format error, and append to string of errors
 * The messages are separated by newline: not perfect.
 */
void starter_error_append(starter_errors_t *perrl, const char *fmt, ...)
{
	va_list args;
	char tmp_err[512];

	va_start(args, fmt);
	vsnprintf(tmp_err, sizeof(tmp_err) - 1, fmt, args);
	va_end(args);

	if (perrl->errors == NULL) {
		/* first error */
		perrl->errors = clone_str(tmp_err, "starter error");
	} else {
		/* subsequent error: append to previous messages */
		size_t ol = strlen(perrl->errors);
		size_t al = strlen(tmp_err);
		char *nerr = alloc_bytes(ol + al + 2, "starter errors");

		memcpy(nerr, perrl->errors, ol);
		nerr[ol] = '\n';
		memcpy(&nerr[ol + 1], tmp_err, al + 1);
		pfree(perrl->errors);
		perrl->errors = nerr;
	}
}

#define KW_POLICY_FLAG(val, fl) { \
		if (conn->options_set[val]) \
			conn->policy = (conn->policy & ~(fl)) | \
				(conn->options[val] ? (fl) : LEMPTY); \
	}

#define KW_POLICY_NEGATIVE_FLAG(val, fl) { \
		if (conn->options_set[val]) { \
			conn->policy = (conn->policy & ~(fl)) | \
				(!conn->options[val] ? (fl) : LEMPTY); \
		} \
	}

/**
 * Create a NULL-terminated array of tokens from a string of whitespace-separated tokens.
 *
 * @param value string to be broken up at blanks, creating strings for list
 * @param n where to place element count (excluding terminating NULL)
 * @return tokens_from_string (NULL or pointer to NULL-terminated array of pointers to strings)
 */
static char **tokens_from_string(const char *value, int *n)
{
	*n = 0;	/* in case of early exit */

	if (value == NULL)
		return NULL;

	/* avoid damaging original string */
	char *const val = clone_str(value, "tokens_from_string value");
	if (val == NULL)
		return NULL;	/* cannot happen -- silence a coverity warning */

	char *const end = val + strlen(val);

	/* count number of items in string and terminate each with NUL */
	int count = 0;
	for (char *b = val; b < end; ) {
		char *e;
		for (e = b; *e != '\0' && !char_isspace(*e); e++)
			;
		*e = '\0';
		if (e != b)
			count++;
		b = e + 1;
	}

	*n = count;

	if (count == 0) {
		pfree(val);
		return NULL;
	}

	char **const nlist = (char **)alloc_bytes((count + 1) * sizeof(char *), "tokens_from_string nlist");

	count = 0;
	for (char *b = val; b < end; ) {
		char *e = b + strlen(b);
		if (e != b)
			nlist[count++] = clone_str(b, "tokens_from_string item");
		b = e + 1;
	}
	nlist[count] = NULL;
	pfree(val);
	return nlist;
}

/**
 * Load a parsed config
 *
 * @param cfg starter_config structure
 * @param cfgp config_parsed (ie: valid) struct
 * @param perr pointer to store errors in
 * @return bool TRUE if unsuccessful
 */
static bool load_setup(struct starter_config *cfg,
		       const struct config_parsed *cfgp)
{
	bool err = FALSE;
	const struct kw_list *kw;

	for (kw = cfgp->config_setup; kw != NULL; kw = kw->next) {
		/**
		 * the parser already made sure that only config keywords were used,
		 * but we double check!
		 */
		assert(kw->keyword.keydef->validity & kv_config);
		unsigned f = kw->keyword.keydef->field;

		switch (kw->keyword.keydef->type) {
		case kt_string:
		case kt_filename:
		case kt_dirname:
		case kt_loose_enum:
			/* all treated as strings for now */
			assert(f < elemsof(cfg->setup.strings));
			pfreeany(cfg->setup.strings[f]);
			cfg->setup.strings[f] =
				clone_str(kw->string, "kt_loose_enum kw->string");
			cfg->setup.strings_set[f] = TRUE;
			break;

		case kt_list:
		case kt_lset:
		case kt_bool:
		case kt_invertbool:
		case kt_enum:
		case kt_number:
		case kt_time:
		case kt_percent:
			/* all treated as a number for now */
			assert(f < elemsof(cfg->setup.options));
			cfg->setup.options[f] = kw->number;
			cfg->setup.options_set[f] = TRUE;
			break;

		case kt_bitstring:
		case kt_rsasigkey:
		case kt_ipaddr:
		case kt_subnet:
		case kt_range:
		case kt_idtype:
			err = TRUE;
			break;

		case kt_comment:
			break;

		case kt_obsolete:
			starter_log(LOG_LEVEL_INFO,
				    "Warning: ignored obsolete keyword '%s'",
				    kw->keyword.keydef->keyname);
			break;
		case kt_obsolete_quiet:
			starter_log(LOG_LEVEL_ERR,
				    "Warning: ignored obsolete keyword '%s'",
				    kw->keyword.keydef->keyname);
			break;
		default:
			/* NEVER HAPPENS */
			break;
		}
	}

	return err;
}

/**
 * Validate that yes in fact we are one side of the tunnel
 *
 * The function checks that IP addresses are valid, nexthops are
 * present (if needed) as well as policies, and sets the leftID
 * from the left= if it isn't set. It sets the conn's sec_label
 * into its left/right struct end options
 *
 * @param conn_st a connection definition
 * @param end a connection end
 * @param leftright const char * "left" or "right"
 * @param perrl pointer to starter_errors_t
 * @return bool TRUE if failed
 */

static bool validate_end(struct starter_conn *conn_st,
			 struct starter_end *end,
			 const char *leftright,
			 starter_errors_t *perrl,
			 struct logger *logger)
{
	err_t er = NULL;
	bool err = FALSE;

	/*
	 * TODO:
	 * The address family default should come in either via
	 * a config setup option, or via gai.conf / RFC3484
	 * For now, %defaultroute and %any means IPv4 only
	 */
	const struct ip_info *hostfam = aftoinfo(conn_st->options[KNCF_HOSTADDRFAMILY]);
	if (hostfam == NULL) {
		const char *ips = end->strings[KNCF_IP];
		hostfam = &ipv4_info;
		if (ips != NULL &&
		    (strchr(ips, ':') != NULL ||
		     streq(ips, "%defaultroute6") ||
		     streq(ips, "%any6"))) {
			hostfam = &ipv6_info;
		}
	}
	pexpect(hostfam == &ipv4_info || hostfam == &ipv6_info); /* i.e., not NULL */

#  define ERR_FOUND(...) { starter_error_append(perrl, __VA_ARGS__); err = TRUE; }

	if (!end->options_set[KNCF_IP])
		conn_st->state = STATE_INCOMPLETE;

	/* validate the KSCF_IP/KNCF_IP */
	end->addrtype = end->options[KNCF_IP];
	switch (end->addrtype) {
	case KH_ANY:
		end->addr = hostfam->address.any;
		break;

	case KH_IFACE:
		/* generally, this doesn't show up at this stage */
		starter_log(LOG_LEVEL_DEBUG, "starter: %s is KH_IFACE", leftright);
		break;

	case KH_IPADDR:
		assert(end->strings[KSCF_IP] != NULL);

		if (end->strings[KSCF_IP][0] == '%') {
			pfree(end->iface);
			end->iface = clone_str(end->strings[KSCF_IP] + 1, "KH_IPADDR end->iface");
			if (!starter_iface_find(end->iface, hostfam,
					       &end->addr,
					       &end->nexthop))
				conn_st->state = STATE_INVALID;
			/* not numeric, so set the type to the iface type */
			end->addrtype = KH_IFACE;
			break;
		}

		er = ttoaddress_num(shunk1(end->strings[KNCF_IP]), hostfam, &end->addr);
		if (er != NULL) {
			/* not an IP address, so set the type to the string */
			end->addrtype = KH_IPHOSTNAME;
		} else {
			hostfam = address_type(&end->addr);
		}

		if (end->id == NULL) {
			ipstr_buf b;

			end->id = clone_str(ipstr(&end->addr, &b), "end if");
		}
		break;

	case KH_OPPO:
		conn_st->policy |= POLICY_OPPORTUNISTIC;
		break;

	case KH_OPPOGROUP:
		conn_st->policy |= POLICY_OPPORTUNISTIC | POLICY_GROUP;
		break;

	case KH_GROUP:
		conn_st->policy |= POLICY_GROUP;
		break;

	case KH_IPHOSTNAME:
		/* generally, this doesn't show up at this stage */
		starter_log(LOG_LEVEL_DEBUG,
			    "starter: %s is KH_IPHOSTNAME", leftright);
		break;

	case KH_DEFAULTROUTE:
		starter_log(LOG_LEVEL_DEBUG,
			    "starter: %s is KH_DEFAULTROUTE", leftright);
		break;

	case KH_NOTSET:
		/* cannot error out here, it might be a partial also= conn */
		break;
	}

	/* now that HOSTFAM has been pined down */
	end->host_family = hostfam;

	if (end->strings_set[KSCF_VTI_IP]) {
		const char *value = end->strings[KSCF_VTI_IP];
		err_t oops = numeric_to_cidr(shunk1(value), NULL, &end->vti_ip);
		if (oops != NULL) {
			ERR_FOUND("bad addr %s%s=%s [%s]",
				  leftright, "vti", value, oops);
		}
		oops = cidr_specified(end->vti_ip);
		if (oops != NULL) {
			ERR_FOUND("bad addr %s%s=%s [%s]",
				  leftright, "vti", value, oops);
		}
		/* XXX: check type? */
	}

	/* validate the KSCF_SUBNET */
	if (end->strings_set[KSCF_SUBNET]) {
		char *value = end->strings[KSCF_SUBNET];

		if (end->strings_set[KSCF_ADDRESSPOOL]) {
			ERR_FOUND("cannot specify both %ssubnet= and %saddresspool=", leftright,
				leftright);
		}

		if (startswith(value, "vhost:") || startswith(value, "vnet:")) {
			if (conn_st->ike_version != IKEv1) {
				ERR_FOUND("The vnet: and vhost: keywords are only valid for IKEv1 connections");
			}

			er = NULL;
			end->virt = clone_str(value, "validate_end item");
		} else {
			end->has_client = TRUE;
			er = ttosubnet(shunk1(value), AF_UNSPEC, '0',
				       &end->subnet, logger);
		}
		if (er != NULL)
			ERR_FOUND("bad subnet %ssubnet=%s [%s]", leftright,
				  value, er);
	}

	/*
	 * validate the KSCF_NEXTHOP; set nexthop address to
	 * something consistent, by default
	 */
	end->nexthop = hostfam->address.any;
	if (end->strings_set[KSCF_NEXTHOP]) {
		char *value = end->strings[KSCF_NEXTHOP];

		if (strcaseeq(value, "%defaultroute")) {
			end->nexttype = KH_DEFAULTROUTE;
		} else {
			ip_address nexthop = unset_address;
#ifdef USE_DNSSEC
			if (ttoaddress_num(shunk1(value), hostfam, &nexthop) != NULL) {
				starter_log(LOG_LEVEL_DEBUG,
					    "Calling unbound_resolve() for %snexthop value",
					    leftright);
				if (!unbound_resolve(value, hostfam, &nexthop, logger))
					ERR_FOUND("bad value for %snexthop=%s\n",
						  leftright, value);
			}
#else
			err_t e = ttoaddress_dns(shunk1(value), hostfam, &nexthop);
			if (e != NULL) {
				ERR_FOUND("bad value for %snexthop=%s [%s]",
					  leftright, value, e);
			}
#endif
			end->nexthop = nexthop;
			end->nexttype = KH_IPADDR;
		}
	} else {
		end->nexthop = hostfam->address.any;

		if (end->addrtype == KH_DEFAULTROUTE) {
			end->nexttype = KH_DEFAULTROUTE;
		}
	}

	/* validate the KSCF_ID */
	if (end->strings_set[KSCF_ID]) {
		char *value = end->strings[KSCF_ID];

		pfreeany(end->id);
		end->id = clone_str(value, "end->id");
		/* fixup old ",," in a ID_DER_ASN1_DN to proper backslash comma */
		if ((end->id[0] != '@') && (strstr(end->id, ",,") != NULL)
			&& strstr(end->id, "=") != NULL)
		{
			char *cc;
			while ((cc = strstr(end->id, ",,")) != NULL) {
				cc[0] = '\\';
			}
		}
	}

	/* Note label is conn option copied to both end's option */
	if (conn_st->strings_set[KSCF_SA_SEC_LABEL]) {
		char *value = conn_st->strings[KSCF_SA_SEC_LABEL];

		pfreeany(end->sec_label);
		end->sec_label = clone_str(value, "end->sec_label");
	}

	if (end->options_set[KSCF_RSASIGKEY]) {
		end->rsasigkey_type = end->options[KSCF_RSASIGKEY];

		switch (end->options[KSCF_RSASIGKEY]) {
		case PUBKEY_DNSONDEMAND:
			end->key_from_DNS_on_demand = TRUE;
			break;

		default:
			end->key_from_DNS_on_demand = FALSE;
			/* validate the KSCF_RSASIGKEY1/RSASIGKEY2 */
			if (end->strings[KSCF_RSASIGKEY] != NULL) {
				char *value = end->strings[KSCF_RSASIGKEY];
				pfreeany(end->rsasigkey);
				end->rsasigkey = clone_str(value, "end->rsasigkey");
			}
		}
	}

	/* validate the KSCF_SOURCEIP, if any, and if set,
	 * set the subnet to same value, if not set.
	 */
	if (end->strings_set[KSCF_SOURCEIP]) {
		char *value = end->strings[KSCF_SOURCEIP];

		/*
		 * XXX: suspect this lookup should be forced to use
		 * the same family as the client.
		 */
		ip_address sourceip = unset_address;
#ifdef USE_DNSSEC
		/* try numeric first */
		err_t e = ttoaddress_num(shunk1(value), NULL/*UNSPEC*/, &sourceip);
		if (e != NULL) {
			starter_log(LOG_LEVEL_DEBUG,
				    "Calling unbound_resolve() for %ssourceip value",
				    leftright);
			if (!unbound_resolve(value, &ipv4_info, &end->sourceip, logger) &&
			    !unbound_resolve(value, &ipv6_info, &end->sourceip, logger))
				ERR_FOUND("bad value for %ssourceip=%s\n",
					  leftright, value);
		}
#else
		/* try numeric then DNS */
		err_t e = ttoaddress_dns(shunk1(value), AF_UNSPEC, &sourceip);
		if (e != NULL) {
			ERR_FOUND("bad addr %ssourceip=%s [%s]",
				  leftright, value, e);
		}
#endif
		end->sourceip = sourceip;
		if (!end->has_client) {
			end->subnet = subnet_from_address(end->sourceip);
			end->has_client = TRUE;
		}
		if (end->strings_set[KSCF_INTERFACE_IP]) {
			ERR_FOUND("cannot specify  %sinterface-ip=%s and  %sssourceip=%s",
					leftright,
					end->strings[KSCF_INTERFACE_IP],
					leftright,
					end->strings[KSCF_SOURCEIP]);
		}
	}

	/* copy certificate path name */
	if (end->strings_set[KSCF_CERT] && end->strings_set[KSCF_CKAID]) {
		ERR_FOUND("only one of %scert and %sckaid can be specified",
			  leftright, leftright);
	}
	if (end->strings_set[KSCF_CERT]) {
		end->certx = clone_str(end->strings[KSCF_CERT], "KSCF_CERT");
	}
	if (end->strings_set[KSCF_CKAID]) {
		const char *ckaid = end->strings[KSCF_CKAID];
		/* try parsing it */
		const char *ugh = ttodata(ckaid, 0, 16, NULL, 0, NULL);
		if (ugh != NULL) {
			ERR_FOUND("invalid %sckaid: %s", leftright, ugh);
		}
		end->ckaid = clone_str(ckaid, "KSCF_CKAID");
	}

	if (end->strings_set[KSCF_CA])
		end->ca = clone_str(end->strings[KSCF_CA], "KSCF_CA");

	if (end->strings_set[KSCF_UPDOWN]) {
		pfreeany(end->updown);
		end->updown = clone_str(end->strings[KSCF_UPDOWN], "KSCF_UPDOWN");
	}

	if (end->strings_set[KSCF_PROTOPORT]) {
		char *value = end->strings[KSCF_PROTOPORT];
		err_t ugh = ttoprotoport(value, &end->protoport);
		if (ugh != NULL)
			ERR_FOUND("bad %sprotoport=%s [%s]", leftright, value,
				  ugh);
	}

	if (end->strings_set[KSCF_ADDRESSPOOL]) {
		char *addresspool = end->strings[KSCF_ADDRESSPOOL];

		if (end->strings_set[KSCF_SUBNET])
			ERR_FOUND("cannot specify both %ssubnet= and %saddresspool=",
				leftright, leftright);
		starter_log(LOG_LEVEL_DEBUG,
			    "connection's %saddresspool set to: %s",
			    leftright, end->strings[KSCF_ADDRESSPOOL] );

		er = ttorange(addresspool, NULL, &end->pool_range);
		if (er != NULL)
			ERR_FOUND("bad %saddresspool=%s [%s]", leftright,
					addresspool, er);

		if (range_type(&end->pool_range) == &ipv6_info &&
		    !end->pool_range.is_subnet) {
			ERR_FOUND("bad IPv6 %saddresspool=%s not subnet", leftright,
					addresspool);
		}
	}

	if (end->strings_set[KSCF_INTERFACE_IP]) {
		const char *value = end->strings[KSCF_INTERFACE_IP];
		err_t oops = numeric_to_cidr(shunk1(value), NULL, &end->ifaceip);
		if (oops != NULL) {
			ERR_FOUND("bad addr %s%s=%s [%s]",
				  leftright, "interface-ip", value, oops);
		}
		oops = cidr_specified(end->ifaceip);
		if (oops != NULL) {
			ERR_FOUND("bad addr %s%s=%s [%s]",
				  leftright, "interface-ip", value, oops);
		}
		if (end->strings_set[KSCF_SOURCEIP]) {
			ERR_FOUND("cannot specify  %sinterface-ip=%s and  %sssourceip=%s",
					leftright,
					end->strings[KSCF_INTERFACE_IP],
					leftright,
					end->strings[KSCF_SOURCEIP]);
		}
	}

	if (end->options_set[KNCF_XAUTHSERVER] ||
	    end->options_set[KNCF_XAUTHCLIENT])
		conn_st->policy |= POLICY_XAUTH;

	return err;
#  undef ERR_FOUND
}

/**
 * Take keywords from ipsec.conf syntax and load into a conn struct
 *
 * @param conn a connection definition
 * @param sl a section_list
 * @param assigned_value is set to either k_set, or k_default.
 *        k_default is used when we are loading a conn that should be
 *        considered to be a "default" value, and that replacing this
 *        value is considered acceptable.
 * @return bool TRUE if unsuccessful
 */
static bool translate_conn(struct starter_conn *conn,
		    const struct section_list *sl,
		    enum keyword_set assigned_value,
		    starter_errors_t *perrl)
{
	/* note: not all errors are considered serious */
	bool serious_err = FALSE;

	for (const struct kw_list *kw = sl->kw; kw != NULL; kw = kw->next) {
		if ((kw->keyword.keydef->validity & kv_conn) == 0) {
			/* this isn't valid in a conn! */
			char tmp_err[512];

			snprintf(tmp_err, sizeof(tmp_err),
				 "keyword '%s' is not valid in a conn (%s)\n",
				 kw->keyword.keydef->keyname, sl->name);
			starter_log(LOG_LEVEL_INFO, "%s", tmp_err);
			starter_error_append(perrl, "%s", tmp_err);
			continue;
		}

		ksf *the_strings;
		str_set *set_strings;
		unsigned str_floor, str_roof;

		knf *the_options;
		int_set *set_options;
		unsigned opt_floor, opt_roof;

		if (kw->keyword.keydef->validity & kv_leftright) {
			struct starter_end *this = kw->keyword.keyleft ?
				&conn->left : &conn->right;

			the_strings = &this->strings;
			set_strings = &this->strings_set;
			str_floor = KSCF_last_loose + 1;
			str_roof = KSCF_last_leftright + 1;

			the_options = &this->options;
			set_options = &this->options_set;
			opt_floor = KSCF_last_loose + 1;
			opt_roof = KNCF_last_leftright + 1;
		} else {
			the_strings = &conn->strings;
			set_strings = &conn->strings_set;
			str_floor = KSCF_last_leftright + 1;
			str_roof = KSCF_ROOF;

			the_options = &conn->options;
			set_options = &conn->options_set;
			opt_floor = KNCF_last_leftright + 1;
			opt_roof = KNCF_ROOF;
		}

		unsigned int field = kw->keyword.keydef->field;

		assert(kw->keyword.keydef != NULL);

		switch (kw->keyword.keydef->type) {
		case kt_string:
		case kt_filename:
		case kt_dirname:
		case kt_bitstring:
		case kt_ipaddr:
		case kt_range:
		case kt_subnet:
		case kt_idtype:
			/* all treated as strings for now, even loose enums */
			assert(field < str_roof);

			if ((*set_strings)[field] == k_set) {
				char tmp_err[512];

				snprintf(tmp_err, sizeof(tmp_err),
					 "duplicate key '%s' in conn %s while processing def %s",
					 kw->keyword.keydef->keyname,
					 conn->name,
					 sl->name);

				starter_log(LOG_LEVEL_INFO, "%s", tmp_err);
				starter_error_append(perrl, "%s", tmp_err);

				/* only fatal if we try to change values */
				if (kw->keyword.string == NULL ||
				    (*the_strings)[field] == NULL ||
				    !streq(kw->keyword.string,
					   (*the_strings)[field]))
				{
					serious_err = TRUE;
					break;
				}
			}
			pfreeany((*the_strings)[field]);

			if (kw->string == NULL) {
				starter_error_append(perrl, "Invalid %s value",
					 kw->keyword.keydef->keyname);
				serious_err = TRUE;
				break;
			}

			(*the_strings)[field] = clone_str(kw->string, "kt_idtype kw->string");
			(*set_strings)[field] = assigned_value;
			break;

		case kt_appendstring:
		case kt_appendlist:
			/* implicitly, this field can have multiple values */
			assert(str_floor <= field && field < str_roof);
			if ((*the_strings)[field] == NULL) {
				(*the_strings)[field] = clone_str(kw->string, "kt_appendlist kw->string");
			} else {
				char *s = (*the_strings)[field];
				size_t old_len = strlen(s);	/* excludes '\0' */
				size_t new_len = strlen(kw->string);
				char *n = alloc_bytes(old_len + 1 + new_len + 1, "kt_appendlist");

				memcpy(n, s, old_len);
				n[old_len] = ' ';
				memcpy(n + old_len + 1, kw->string, new_len + 1);	/* includes '\0' */
				(*the_strings)[field] = n;
				pfree(s);
			}
			(*set_strings)[field] = TRUE;
			break;

		case kt_rsasigkey:
		case kt_loose_enum:
			assert(field <= KSCF_last_loose);

			if ((*set_options)[field] == k_set) {
				char tmp_err[512];

				snprintf(tmp_err, sizeof(tmp_err),
					 "duplicate key '%s' in conn %s while processing def %s",
					 kw->keyword.keydef->keyname,
					 conn->name,
					 sl->name);

				starter_log(LOG_LEVEL_INFO, "%s", tmp_err);
				starter_error_append(perrl, "%s", tmp_err);

				/* only fatal if we try to change values */
				if ((*the_options)[field] != (int)kw->number ||
				    !((*the_options)[field] ==
					LOOSE_ENUM_OTHER &&
				      kw->number == LOOSE_ENUM_OTHER &&
				      kw->keyword.string != NULL &&
				      (*the_strings)[field] != NULL &&
				      streq(kw->keyword.string,
					     (*the_strings)[field])))
				{
					serious_err = TRUE;
					break;
				}
			}

			(*the_options)[field] = kw->number;
			if (kw->number == LOOSE_ENUM_OTHER) {
				assert(kw->keyword.string != NULL);
				pfreeany((*the_strings)[field]);
				(*the_strings)[field] = clone_str(
					kw->keyword.string, "kt_loose_enum kw->keyword.string");
			}
			(*set_options)[field] = assigned_value;
			break;

		case kt_list:
		case kt_lset:
		case kt_bool:
		case kt_invertbool:
		case kt_enum:
		case kt_number:
		case kt_time:
		case kt_percent:
			/* all treated as a number for now */
			assert(opt_floor <= field && field < opt_roof);

			if ((*set_options)[field] == k_set) {
				char tmp_err[512];

				snprintf(tmp_err, sizeof(tmp_err),
					 "duplicate key '%s' in conn %s while processing def %s",
					 kw->keyword.keydef->keyname,
					 conn->name,
					 sl->name);
				starter_log(LOG_LEVEL_INFO, "%s", tmp_err);
				starter_error_append(perrl, "%s", tmp_err);
				/* only fatal if we try to change values */
				if ((*the_options)[field] != (int)kw->number) {
					serious_err = TRUE;
					break;
				}
			}

			(*the_options)[field] = kw->number;
			(*set_options)[field] = assigned_value;
			break;

		case kt_comment:
			break;

		case kt_obsolete:
			starter_log(LOG_LEVEL_INFO,
				    "Warning: obsolete keyword '%s' ignored",
				    kw->keyword.keydef->keyname);
			break;

		case kt_obsolete_quiet:
			starter_log(LOG_LEVEL_ERR,
				    "Warning: obsolete keyword '%s' ignored",
				    kw->keyword.keydef->keyname);
			break;
		}
	}
	return serious_err;
}

static void move_comment_list(struct starter_comments_list *to,
		       struct starter_comments_list *from)
{
	struct starter_comments *sc, *scnext;

	for (sc = from->tqh_first;
	     sc != NULL;
	     sc = scnext) {
		scnext = sc->link.tqe_next;
		TAILQ_REMOVE(from, sc, link);
		TAILQ_INSERT_TAIL(to, sc, link);
	}
}

static bool load_conn(struct starter_conn *conn,
		      const struct config_parsed *cfgp,
		      struct section_list *sl,
		      bool alsoprocessing,
		      bool defaultconn,
		      starter_errors_t *perrl,
		      struct logger *logger)
{
	bool err;

	/* turn all of the keyword/value pairs into options/strings in left/right */
	err = translate_conn(conn, sl,
			defaultconn ? k_default : k_set,
			perrl);

	move_comment_list(&conn->comments, &sl->comments);

	if (err)
		return err;

	if (conn->strings[KSCF_ALSO] != NULL &&
	    !alsoprocessing) {
		starter_log(LOG_LEVEL_INFO,
			    "also= is not valid in section '%s'",
			    sl->name);
		starter_error_append(perrl, "also= is not valid in section '%s'",
			sl->name);
		return TRUE;	/* error */
	}

	/*
	 * Process the also list
	 *
	 * Note: conn->alsos will be NULL until we finish
	 * and the appropriate list will be in local variable alsos.
	 */

	/* free any residual alsos list */
	if (conn->alsos != NULL) {
		for (char **s = conn->alsos; *s != NULL; s++)
			pfreeany(*s);
		pfree(conn->alsos);
		conn->alsos = NULL;
	}

	int alsosize;
	char **alsos = tokens_from_string(conn->strings[KSCF_ALSO], &alsosize);

	if (alsoprocessing && alsos != NULL) {
		/* reset all of the "beenhere" flags */
		for (struct section_list *s = cfgp->sections.tqh_first; s != NULL;
		     s = s->link.tqe_next)
			s->beenhere = FALSE;
		sl->beenhere = TRUE;

		for (int alsoplace = 0; alsoplace < alsosize; alsoplace++) {
			/*
			 * Check for too many alsos.
			 * Inside the loop because of indirect alsos.
			 */
			if (alsosize >= ALSO_LIMIT) {
				starter_log(LOG_LEVEL_INFO,
					    "while loading conn '%s', too many also= used at section %s. Limit is %d",
					    conn->name,
					    alsos[alsosize],
					    ALSO_LIMIT);
				starter_error_append(perrl, "while loading conn '%s', too many also= used at section %s. Limit is %d",
					conn->name,
					alsos[alsosize],
					ALSO_LIMIT);
				return TRUE;	/* error */
			}

			/*
			 * for each also= listed, go find this section's keyword list, and
			 * load it as well. This may extend the also= list (and the end),
			 * which we handle by zeroing the also list, and adding to it after
			 * checking for duplicates.
			 */
			struct section_list *addin;

			for (addin = cfgp->sections.tqh_first;
			     addin != NULL &&
			     !streq(alsos[alsoplace], addin->name);
			     addin = addin->link.tqe_next)
				;

			if (addin == NULL) {
				starter_log(LOG_LEVEL_ERR,
					    "cannot find conn '%s' needed by conn '%s'",
					    alsos[alsoplace], conn->name);
				starter_error_append(perrl, "cannot find conn '%s' needed by conn '%s'",
					alsos[alsoplace], conn->name);
				err = TRUE;
				continue;	/* allowing further error detection */
			}

			if (addin->beenhere)
				continue;	/* already handled */

			starter_log(LOG_LEVEL_DEBUG,
				    "\twhile loading conn '%s' also including '%s'",
				    conn->name, alsos[alsoplace]);

			conn->strings_set[KSCF_ALSO] = FALSE;
			pfreeany(conn->strings[KSCF_ALSO]);
			conn->strings[KSCF_ALSO] = NULL;
			addin->beenhere = TRUE;

			/* translate things, but do not replace earlier settings! */
			err |= translate_conn(conn, addin, k_set, perrl);

			if (conn->strings[KSCF_ALSO] != NULL) {
				/* add this guy's alsos too */
				int newalsosize;
				char **newalsos = tokens_from_string(
					conn->strings[KSCF_ALSO], &newalsosize);

				if (newalsos != NULL) {
					/*
					 * Append newalsos onto alsos.
					 * Requires a re-allocation.
					 * Copying is shallow: the lists
					 * are copied and freed but
					 * the underlying strings are unchanged.
					 */
					char **ra = alloc_bytes((alsosize +
						newalsosize + 1) *
						sizeof(char *),
						"conn->alsos");
					memcpy(ra, alsos, alsosize * sizeof(char *));
					pfree(alsos);
					alsos = ra;

					memcpy(ra + alsosize, newalsos,
						(newalsosize + 1) * sizeof(char *));
					pfree(newalsos);

					alsosize += newalsosize;
				}
			}
		}
	}

	/*
	 * Migrate alsos back to conn->alsos.
	 * Note that this is the transitive closure.
	 */
	conn->alsos = alsos;

	if (conn->options_set[KNCF_TYPE]) {
		switch ((enum keyword_satype)conn->options[KNCF_TYPE]) {
		case KS_TUNNEL:
			conn->policy &= ~POLICY_SHUNT_MASK;
			conn->policy |= POLICY_TUNNEL | POLICY_SHUNT_TRAP;
			break;

		case KS_TRANSPORT:
			conn->policy &= ~POLICY_TUNNEL & ~POLICY_SHUNT_MASK;
			conn->policy |=  POLICY_SHUNT_TRAP;
			break;

		case KS_PASSTHROUGH:
			conn->policy &=
				~(POLICY_ENCRYPT | POLICY_AUTHENTICATE |
				  POLICY_TUNNEL | POLICY_RSASIG |
				  POLICY_SHUNT_MASK);
			conn->policy |= POLICY_SHUNT_PASS;
			break;

		case KS_DROP:
			conn->policy &=
				~(POLICY_ENCRYPT | POLICY_AUTHENTICATE |
				  POLICY_TUNNEL | POLICY_RSASIG |
				  POLICY_SHUNT_MASK);
			conn->policy |= POLICY_SHUNT_DROP;
			break;

		case KS_REJECT:
			conn->policy &=
				~(POLICY_ENCRYPT | POLICY_AUTHENTICATE |
				  POLICY_TUNNEL | POLICY_RSASIG |
				  POLICY_SHUNT_MASK);
			conn->policy |= POLICY_SHUNT_REJECT;
			break;
		}
	}

	if (conn->options_set[KNCF_FAILURESHUNT]) {
		conn->policy &= ~POLICY_FAIL_MASK;
		switch (conn->options[KNCF_FAILURESHUNT]) {
		case KFS_FAIL_NONE:
			conn->policy |= POLICY_FAIL_NONE;
			break;
		case KFS_FAIL_PASS:
			conn->policy |= POLICY_FAIL_PASS;
			break;
		case KFS_FAIL_DROP:
			conn->policy |= POLICY_FAIL_DROP;
			break;
		case KFS_FAIL_REJECT:
			conn->policy |= POLICY_FAIL_REJECT;
			break;
		}
	}

	if (conn->options_set[KNCF_NEGOTIATIONSHUNT]) {
		switch (conn->options[KNCF_NEGOTIATIONSHUNT]) {
		case KNS_FAIL_PASS:
			conn->policy |= POLICY_NEGO_PASS;
			break;
		case KNS_FAIL_DROP:
			conn->policy &= ~POLICY_NEGO_PASS;
			break;
		}
	}

	KW_POLICY_FLAG(KNCF_COMPRESS, POLICY_COMPRESS);
	KW_POLICY_FLAG(KNCF_PFS, POLICY_PFS);

	/* reset authby= flags */
	if (conn->strings_set[KSCF_AUTHBY]) {

		conn->policy &= ~POLICY_ID_AUTH_MASK;
		conn->sighash_policy = LEMPTY;
	}

	KW_POLICY_NEGATIVE_FLAG(KNCF_IKEPAD, POLICY_NO_IKEPAD);

	KW_POLICY_NEGATIVE_FLAG(KNCF_REKEY, POLICY_DONT_REKEY);
	KW_POLICY_FLAG(KNCF_REAUTH, POLICY_REAUTH);

	KW_POLICY_FLAG(KNCF_AGGRMODE, POLICY_AGGRESSIVE);

	KW_POLICY_FLAG(KNCF_MODECONFIGPULL, POLICY_MODECFG_PULL);

	KW_POLICY_FLAG(KNCF_OVERLAPIP, POLICY_OVERLAPIP);

	KW_POLICY_FLAG(KNCF_IKEv2_ALLOW_NARROWING,
		       POLICY_IKEV2_ALLOW_NARROWING);

	KW_POLICY_FLAG(KNCF_MOBIKE, POLICY_MOBIKE);

	KW_POLICY_FLAG(KNCF_IKEv2_PAM_AUTHORIZE,
		       POLICY_IKEV2_PAM_AUTHORIZE);

	KW_POLICY_FLAG(KNCF_DECAP_DSCP, POLICY_DECAP_DSCP);
	KW_POLICY_FLAG(KNCF_NOPMTUDISC, POLICY_NOPMTUDISC);
	KW_POLICY_FLAG(KNCF_MSDH_DOWNGRADE, POLICY_MSDH_DOWNGRADE);
	KW_POLICY_FLAG(KNCF_DNS_MATCH_ID, POLICY_DNS_MATCH_ID);
	KW_POLICY_FLAG(KNCF_SHA2_TRUNCBUG, POLICY_SHA2_TRUNCBUG);
	KW_POLICY_FLAG(KNCF_INTERMEDIATE, POLICY_INTERMEDIATE);
	KW_POLICY_FLAG(KNCF_IGNORE_PEER_DNS, POLICY_IGNORE_PEER_DNS);

	if (conn->options_set[KNCF_SAN_ON_CERT]) {
		if (!conn->options[KNCF_SAN_ON_CERT])
			conn->policy |= POLICY_ALLOW_NO_SAN;
	}

	/* ??? sometimes (when? why?) the member is already set */

#	define str_to_conn(member, kscf) { \
		if (conn->strings_set[kscf]) { \
			pfreeany(conn->member); \
			conn->member = clone_str(conn->strings[kscf], #kscf); \
		} \
	}

	str_to_conn(connalias, KSCF_CONNALIAS);

	str_to_conn(ike_crypto, KSCF_IKE);
	str_to_conn(esp, KSCF_ESP);

	str_to_conn(modecfg_dns, KSCF_MODECFGDNS);
	str_to_conn(modecfg_domains, KSCF_MODECFGDOMAINS);
	str_to_conn(modecfg_banner, KSCF_MODECFGBANNER);

	str_to_conn(sec_label, KSCF_SA_SEC_LABEL); /* copied to left/right later */

	str_to_conn(conn_mark_both, KSCF_CONN_MARK_BOTH);
	str_to_conn(conn_mark_in, KSCF_CONN_MARK_IN);
	str_to_conn(conn_mark_out, KSCF_CONN_MARK_OUT);
	str_to_conn(vti_iface, KSCF_VTI_IFACE);

	str_to_conn(redirect_to, KSCF_REDIRECT_TO);
	str_to_conn(accept_redirect_to, KSCF_ACCEPT_REDIRECT_TO);

#	undef str_to_conn

	if (conn->options_set[KNCF_PHASE2]) {
		conn->policy &= ~(POLICY_AUTHENTICATE | POLICY_ENCRYPT);
		conn->policy |= conn->options[KNCF_PHASE2];
	}

	/*
	 * This option has really been turned into a boolean, but
	 * we need the keywords for backwards compatibility for now
	 */
	if (conn->options_set[KNCF_IKEv2]) {

		switch (conn->options[KNCF_IKEv2]) {
		case fo_never:
		case fo_permit:
			conn->ike_version = IKEv1;
			break;

		case fo_propose:
		case fo_insist:
			conn->ike_version = IKEv2;
			break;
		}
	}

	if (conn->options_set[KNCF_SEND_REDIRECT]) {
		if (conn->ike_version >= IKEv2) {
			switch (conn->options[KNCF_SEND_REDIRECT]) {
			case yna_yes:
				conn->policy |= POLICY_SEND_REDIRECT_ALWAYS;
				if (conn->redirect_to == NULL) {
					starter_log(LOG_LEVEL_INFO,
					"redirect-to is not specified, although send-redirect is set to yes");
				}
				break;

			case yna_no:
				conn->policy |= POLICY_SEND_REDIRECT_NEVER;
				break;

			case yna_auto:
				break;
			}
		}
	}

	if (conn->options_set[KNCF_ACCEPT_REDIRECT]) {
		if (conn->ike_version >= IKEv2) {
			switch (conn->options[KNCF_ACCEPT_REDIRECT]) {
			case yna_yes:
				conn->policy |= POLICY_ACCEPT_REDIRECT_YES;
				break;

			/* default policy is no, so there is no POLICY_ACCEPT_REDIRECT_YES
			 * in policy.
			 *
			 * technically the values for this option are yes/no,
			 * although we use yna option set (we do not want to
			 * make new yes-no enum)
			 */
			case yna_auto:
			case yna_no:
				break;
			}
		}
	}

	if (conn->options_set[KNCF_PPK]) {
		lset_t ppk = LEMPTY;

		if (conn->ike_version >= IKEv2) {
			switch (conn->options[KNCF_PPK]) {
			case fo_propose:
				ppk = POLICY_PPK_ALLOW;
				break;

			case fo_permit:
				ppk = POLICY_PPK_ALLOW;
				break;

			case fo_insist:
				ppk = POLICY_PPK_ALLOW | POLICY_PPK_INSIST;
				break;

			case fo_never:
				break;
			}
		}
		conn->policy = conn->policy | ppk;
	}

	if (conn->options_set[KNCF_ESN]) {
		conn->policy &= ~(POLICY_ESN_NO | POLICY_ESN_YES);

		switch (conn->options[KNCF_ESN]) {
		case ESN_YES:
			conn->policy |= POLICY_ESN_YES;
			break;

		case ESN_NO:
			/* this is the default for now */
			conn->policy |= POLICY_ESN_NO;
			break;

		case ESN_EITHER:
			conn->policy |= POLICY_ESN_NO | POLICY_ESN_YES;
			break;
		}
	}

	if (conn->options_set[KNCF_IKE_FRAG]) {
		conn->policy &= ~(POLICY_IKE_FRAG_ALLOW | POLICY_IKE_FRAG_FORCE);

		switch (conn->options[KNCF_IKE_FRAG]) {
		case ynf_no:
			break;

		case ynf_yes:
			/* this is the default */
			conn->policy |= POLICY_IKE_FRAG_ALLOW;
			break;

		case ynf_force:
			conn->policy |= POLICY_IKE_FRAG_ALLOW |
					POLICY_IKE_FRAG_FORCE;
			break;
		}
	}

	/* read in the authby string and translate to policy bits
	 * this is the symmetric (left+right) version
	 * there is also leftauthby/rightauthby version stored in 'end'
	 *
	 * authby=secret|rsasig|null|never|rsa-HASH
	 *
	 * using authby=rsasig results in legacy POLICY_RSASIG_v1_5 and RSA_PSS
	 *
	 * HASH needs to use full syntax - eg sha2_256 and not sha256, to avoid
	 * confusion with sha3_256
	 */
	if (conn->strings_set[KSCF_AUTHBY]) {
		char *val = strtok(conn->strings[KSCF_AUTHBY], ", ");

		conn->sighash_policy = LEMPTY;
		conn->policy &= ~POLICY_ID_AUTH_MASK;
		conn->policy &= ~POLICY_RSASIG_v1_5;

		while (val != NULL) {
			/* Supported for IKEv1 and IKEv2 */
			if (streq(val, "secret")) {
				conn->policy |= POLICY_PSK;
			} else if (streq(val, "rsasig") || streq(val, "rsa")) {
				conn->policy |= POLICY_RSASIG;
				conn->policy |= POLICY_RSASIG_v1_5;
				conn->sighash_policy |= POL_SIGHASH_SHA2_256;
				conn->sighash_policy |= POL_SIGHASH_SHA2_384;
				conn->sighash_policy |= POL_SIGHASH_SHA2_512;
			} else if (streq(val, "never")) {
				conn->policy |= POLICY_AUTH_NEVER;
			/* everything else is only supported for IKEv2 */
			} else if (conn->ike_version == IKEv1) {
				starter_error_append(perrl, "ikev1 connection must use authby= of rsasig, secret or never");
				return TRUE;
			} else if (streq(val, "null")) {
				conn->policy |= POLICY_AUTH_NULL;
			} else if (streq(val, "rsa-sha1")) {
				conn->policy |= POLICY_RSASIG;
				conn->policy |= POLICY_RSASIG_v1_5;
			} else if (streq(val, "rsa-sha2")) {
				conn->policy |= POLICY_RSASIG;
				conn->sighash_policy |= POL_SIGHASH_SHA2_256;
				conn->sighash_policy |= POL_SIGHASH_SHA2_384;
				conn->sighash_policy |= POL_SIGHASH_SHA2_512;
			} else if (streq(val, "rsa-sha2_256")) {
				conn->sighash_policy |= POL_SIGHASH_SHA2_256;
			} else if (streq(val, "rsa-sha2_384")) {
				conn->policy |= POLICY_RSASIG;
				conn->sighash_policy |= POL_SIGHASH_SHA2_384;
			} else if (streq(val, "rsa-sha2_512")) {
				conn->policy |= POLICY_RSASIG;
				conn->sighash_policy |= POL_SIGHASH_SHA2_512;
			} else if (streq(val, "ecdsa") || streq(val, "ecdsa-sha2")) {
				conn->policy |= POLICY_ECDSA;
				conn->sighash_policy |= POL_SIGHASH_SHA2_256;
				conn->sighash_policy |= POL_SIGHASH_SHA2_384;
				conn->sighash_policy |= POL_SIGHASH_SHA2_512;
			} else if (streq(val, "ecdsa-sha2_256")) {
				conn->policy |= POLICY_ECDSA;
				conn->sighash_policy |= POL_SIGHASH_SHA2_256;
			} else if (streq(val, "ecdsa-sha2_384")) {
				conn->policy |= POLICY_ECDSA;
				conn->sighash_policy |= POL_SIGHASH_SHA2_384;
			} else if (streq(val, "ecdsa-sha2_512")) {
				conn->policy |= POLICY_ECDSA;
				conn->sighash_policy |= POL_SIGHASH_SHA2_512;
			} else if (streq(val, "ecdsa-sha1")) {
				starter_error_append(perrl, "authby=ecdsa cannot use sha1, only sha2");
				return TRUE;
			} else {
				starter_error_append(perrl, "connection authby= value is unknown");
				return TRUE;
			}
			val = strtok(NULL, ", ");
		}
	}

	/*
	 * some options are set as part of our default, but
	 * some make no sense for shunts, so remove those again
	 */
	if (NEVER_NEGOTIATE(conn->policy)) {
		/* remove IPsec related options */
		conn->ike_version = 0;
		conn->policy &= (~(POLICY_PFS |
				   POLICY_COMPRESS |
				   POLICY_ESN_NO |
				   POLICY_ESN_YES |
				   POLICY_DECAP_DSCP |
				   POLICY_NOPMTUDISC) &
				 /* remove IKE related options */
				 ~(POLICY_IKE_FRAG_ALLOW |
				   POLICY_IKE_FRAG_FORCE));
	}

	err |= validate_end(conn, &conn->left, "left", perrl, logger);
	err |= validate_end(conn, &conn->right, "right", perrl, logger);

	/*
	 * TODO:
	 * verify both ends are using the same inet family, if one end
	 * is "%any" or "%defaultroute", then perhaps adjust it.
	 * ensource this for left,leftnexthop,right,rightnexthop
	 */

	if (conn->options_set[KNCF_AUTO])
		conn->desired_state = conn->options[KNCF_AUTO];

	if (conn->desired_state == STARTUP_KEEP)
		conn->policy |= POLICY_UP; /* auto=keep means once up, keep up */

	return err;
}

static void copy_conn_default(struct starter_conn *conn,
			      const struct starter_conn *def)
{
	/* structure copy to start */
	*conn = *def;

	/* unlink it */
	conn->link.tqe_next = NULL;
	conn->link.tqe_prev = NULL;

	/* Unshare all strings */

	/*
	 * Note: string fields in struct starter_end and struct starter_conn
	 * should correspond to STR_FIELD calls in copy_conn_default() and confread_free_conn.
	 */

	assert(conn->connalias == NULL);

# define STR_FIELD(f)  { conn->f = clone_str(conn->f, #f); }

	STR_FIELD(name);
	STR_FIELD(connalias);

	STR_FIELD(ike_crypto);
	STR_FIELD(esp);

	STR_FIELD(modecfg_dns);
	STR_FIELD(modecfg_domains);
	STR_FIELD(modecfg_banner);
	STR_FIELD(conn_mark_both);
	STR_FIELD(conn_mark_in);
	STR_FIELD(conn_mark_out);
	STR_FIELD(sec_label);
	STR_FIELD(conn_mark_both);
	STR_FIELD(conn_mark_in);
	STR_FIELD(conn_mark_out);
	STR_FIELD(vti_iface);
	STR_FIELD(redirect_to);
	STR_FIELD(accept_redirect_to);

	for (unsigned i = 0; i < elemsof(conn->strings); i++)
		STR_FIELD(strings[i]);

	/* handle starter_end strings */

# define STR_FIELD_END(f) { STR_FIELD(left.f); STR_FIELD(right.f); }

	STR_FIELD_END(iface);
	STR_FIELD_END(id);
	STR_FIELD_END(sec_label);
	STR_FIELD_END(rsasigkey);
	STR_FIELD_END(virt);
	STR_FIELD_END(certx);
	STR_FIELD_END(ckaid);
	STR_FIELD_END(ca);
	STR_FIELD_END(updown);

	for (unsigned i = 0; i < elemsof(conn->left.strings); i++)
		STR_FIELD_END(strings[i]);

# undef STR_FIELD_END

# undef STR_FIELD
}

static struct starter_conn *alloc_add_conn(struct starter_config *cfg, const char *name)
{
	struct starter_conn *conn = alloc_thing(struct starter_conn, "add_conn starter_conn");

	copy_conn_default(conn, &cfg->conn_default);
	assert(conn->name == NULL);
	conn->name = clone_str(name, "add conn name");
	conn->desired_state = STARTUP_IGNORE;
	conn->state = STATE_FAILED;

	TAILQ_INIT(&conn->comments);

	TAILQ_INSERT_TAIL(&cfg->conns, conn, link);
	return conn;
}

static bool init_load_conn(struct starter_config *cfg,
			   const struct config_parsed *cfgp,
			   struct section_list *sconn,
			   bool defaultconn,
			   starter_errors_t *perrl,
			   struct logger *logger)
{
	starter_log(LOG_LEVEL_DEBUG, "Loading conn %s", sconn->name);

	struct starter_conn *conn = alloc_add_conn(cfg, sconn->name);

	bool connerr = load_conn(conn, cfgp, sconn, TRUE,
				 defaultconn, perrl, logger);

	if (connerr) {
		starter_log(LOG_LEVEL_INFO, "while loading '%s': %s",
			    sconn->name, perrl->errors);
		/* ??? should caller not log perrl? */
	} else {
		conn->state = STATE_LOADED;
	}
	return connerr;
}

struct starter_config *confread_load(const char *file,
				     starter_errors_t *perrl,
				     const char *ctlsocket,
				     bool setuponly,
				     struct logger *logger)
{
	bool err = FALSE;

	/**
	 * Load file
	 */
	struct config_parsed *cfgp = parser_load_conf(file, perrl);

	if (cfgp == NULL)
		return NULL;

	struct starter_config *cfg = alloc_thing(struct starter_config, "starter_config cfg");

	/**
	 * Set default values
	 */
	ipsecconf_default_values(cfg);

	if (ctlsocket != NULL) {
		pfree(cfg->ctlsocket);
		cfg->ctlsocket = clone_str(ctlsocket, "default ctlsocket");
	}

	/**
	 * Load setup
	 */
	err |= load_setup(cfg, cfgp);

	if (err) {
		parser_free_conf(cfgp);
		confread_free(cfg);
		return NULL;
	}

	if (!setuponly) {
#ifdef USE_DNSSEC
		unbound_sync_init(cfg->setup.options[KBF_DO_DNSSEC],
				  cfg->setup.strings[KSF_PLUTO_DNSSEC_ROOTKEY_FILE],
				  cfg->setup.strings[KSF_PLUTO_DNSSEC_ANCHORS],
				  logger);
#endif

		/*
		 * Load %default conn
		 * ??? is it correct to accept multiple %default conns?
		 */
		for (struct section_list *sconn = cfgp->sections.tqh_first; (!err) && sconn != NULL;
		     sconn = sconn->link.tqe_next) {
			if (streq(sconn->name, "%default")) {
				starter_log(LOG_LEVEL_DEBUG,
					    "Loading default conn");
				err |= load_conn(&cfg->conn_default,
						 cfgp, sconn, false,
						 true/*default conn*/,
						 perrl, logger);
			}
		}

		/*
		 * Load other conns
		 */
		for (struct section_list *sconn = cfgp->sections.tqh_first; sconn != NULL;
		     sconn = sconn->link.tqe_next) {
			if (!streq(sconn->name, "%default"))
				err |= init_load_conn(cfg, cfgp, sconn,
						      false/*default conn*/,
						      perrl, logger);
		}
	}

	parser_free_conf(cfgp);
#ifdef USE_DNSSEC
	unbound_ctx_free();
#endif
	return cfg;
}

static void confread_free_conn(struct starter_conn *conn)
{
	/* Free all strings */

	/*
	 * Note: string fields in struct starter_end and struct starter_conn
	 * should correspond to STR_FIELD calls in copy_conn_default() and confread_free_conn.
	 */

# define STR_FIELD(f)  { pfreeany(conn->f); }

	STR_FIELD(name);
	STR_FIELD(connalias);

	STR_FIELD(ike_crypto);
	STR_FIELD(esp);

	STR_FIELD(modecfg_dns);
	STR_FIELD(modecfg_domains);
	STR_FIELD(modecfg_banner);
	STR_FIELD(conn_mark_both);
	STR_FIELD(conn_mark_in);
	STR_FIELD(conn_mark_out);
	STR_FIELD(sec_label);
	STR_FIELD(conn_mark_both);
	STR_FIELD(conn_mark_in);
	STR_FIELD(conn_mark_out);
	STR_FIELD(vti_iface);
	STR_FIELD(redirect_to);
	STR_FIELD(accept_redirect_to);

	for (unsigned i = 0; i < elemsof(conn->strings); i++)
		STR_FIELD(strings[i]);

	/* handle starter_end strings */

# define STR_FIELD_END(f) { STR_FIELD(left.f); STR_FIELD(right.f); }

	STR_FIELD_END(iface);
	STR_FIELD_END(id);
	STR_FIELD_END(sec_label);
	STR_FIELD_END(rsasigkey);
	STR_FIELD_END(virt);
	STR_FIELD_END(certx);
	STR_FIELD_END(ckaid);
	STR_FIELD_END(ca);
	STR_FIELD_END(updown);

	for (unsigned i = 0; i < elemsof(conn->left.strings); i++)
		STR_FIELD_END(strings[i]);

# undef STR_FIELD_END

# undef STR_FIELD
}

void confread_free(struct starter_config *cfg)
{
	pfree(cfg->ctlsocket);

	for (unsigned i = 0; i < elemsof(cfg->setup.strings); i++)
		pfreeany(cfg->setup.strings[i]);

	confread_free_conn(&cfg->conn_default);

	for (struct starter_conn *conn = cfg->conns.tqh_first; conn != NULL; ) {
		struct starter_conn *c = conn;

		conn = conn->link.tqe_next;
		confread_free_conn(c);
		pfree(c);
	}
	pfree(cfg);
}
